/******************************************************************************
 *
 * Copyright (c) 1998,99 by Mindbright Technology AB, Stockholm, Sweden.
 *                 www.mindbright.se, info@mindbright.se
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *****************************************************************************
 * $Author: marcus $
 * $Date: 2000/12/20 21:48:30 $
 * $Name:  $
 *****************************************************************************/
/*
 * !!! Author's comment: The contents of this file is heavily based
 * upon Tatu Ylonen's c-code (from the ssh1 package). His comments follows:
 * ...
 * This code is based on Xuejia Lai: On the Design and Security of Block
 * Ciphers, ETH Series in Information Processing, vol. 1, Hartung-Gorre
 * Verlag, Konstanz, Switzerland, 1992.  Another source was Bruce
 * Schneier: Applied Cryptography, John Wiley & Sons, 1994.
 *
 * The IDEA mathematical formula may be covered by one or more of the
 * following patents: PCT/CH91/00117, EP 0 482 154 B1, US Pat. 5,214,703.
 */

package BLK.System.Utils.Crypto;

public final class IDEA extends Cipher {

  protected int[]   key_schedule = new int[52];
  protected int     IV0 = 0;
  protected int     IV1 = 0;

  public synchronized void encrypt(byte[] src, int srcOff, byte[] dest, int destOff, int len) {
    int[]  out = new int[2];
    int iv0 = IV0;
    int iv1 = IV1;
    int end = srcOff + len;

    for(int si = srcOff, di = destOff; si < end; si += 8, di += 8) {
      encrypt(iv0, iv1, out);
      iv0 = out[0];
      iv1 = out[1];
      iv0 ^= ((src[si + 3] & 0xff) | ((src[si + 2] & 0xff) << 8) |
	      ((src[si + 1] & 0xff) << 16) | ((src[si] & 0xff) << 24));
      iv1 ^= ((src[si + 7] & 0xff) | ((src[si + 6] & 0xff) << 8) |
	      ((src[si + 5] & 0xff) << 16) | ((src[si + 4] & 0xff) << 24));

      if(di + 8 <= end) {
	  dest[di+3] = (byte)( iv0         & 0xff);
	  dest[di+2] = (byte)((iv0 >>> 8 ) & 0xff);
	  dest[di+1] = (byte)((iv0 >>> 16) & 0xff);
	  dest[di]   = (byte)((iv0 >>> 24) & 0xff);
	  dest[di+7] = (byte)( iv1         & 0xff);
	  dest[di+6] = (byte)((iv1 >>> 8 ) & 0xff);
	  dest[di+5] = (byte)((iv1 >>> 16) & 0xff);
	  dest[di+4] = (byte)((iv1 >>> 24) & 0xff);
      } else {
	  switch(end - di) {
	  case 7:
	      dest[di+6] = (byte)((iv1 >>> 8) & 0xff);
	  case 6:
	      dest[di+5] = (byte)((iv1 >>> 16) & 0xff);
	  case 5:
	      dest[di+4] = (byte)((iv1 >>> 24) & 0xff);
	  case 4:
	      dest[di+3] = (byte)( iv0         & 0xff);
	  case 3:
	      dest[di+2] = (byte)((iv0 >>> 8) & 0xff);
	  case 2:
	      dest[di+1] = (byte)((iv0 >>> 16) & 0xff);
	  case 1:
	      dest[di]   = (byte)((iv0 >>> 24) & 0xff);
	  }
      }
    }
    IV0 = iv0;
    IV1 = iv1;
  }

  public synchronized void decrypt(byte[] src, int srcOff, byte[] dest, int destOff, int len) {
    int[]  out = new int[2];
    int iv0 = IV0;
    int iv1 = IV1;
    int plain0, plain1;
    int end = srcOff + len;

    for(int si = srcOff, di = destOff; si < end; si += 8, di += 8) {
      decrypt(iv0, iv1, out);
      iv0 = ((src[si + 3] & 0xff) | ((src[si + 2] & 0xff) << 8) |
	      ((src[si + 1] & 0xff) << 16) | ((src[si] & 0xff) << 24));
      iv1 = ((src[si + 7] & 0xff) | ((src[si + 6] & 0xff) << 8) |
	      ((src[si + 5] & 0xff) << 16) | ((src[si + 4] & 0xff) << 24));
      plain0 = out[0] ^ iv0;
      plain1 = out[1] ^ iv1;

      if(di + 8 <= end) {
	  dest[di+3] = (byte)( plain0         & 0xff);
	  dest[di+2] = (byte)((plain0 >>> 8 ) & 0xff);
	  dest[di+1] = (byte)((plain0 >>> 16) & 0xff);
	  dest[di]   = (byte)((plain0 >>> 24) & 0xff);
	  dest[di+7] = (byte)( plain1         & 0xff);
	  dest[di+6] = (byte)((plain1 >>> 8 ) & 0xff);
	  dest[di+5] = (byte)((plain1 >>> 16) & 0xff);
	  dest[di+4] = (byte)((plain1 >>> 24) & 0xff);
      } else {
	  switch(end - di) {
	  case 7:
	      dest[di+6] = (byte)((plain1 >>> 8) & 0xff);
	  case 6:
	      dest[di+5] = (byte)((plain1 >>> 16) & 0xff);
	  case 5:
	      dest[di+4] = (byte)((plain1 >>> 24) & 0xff);
	  case 4:
	      dest[di+3] = (byte)( plain0         & 0xff);
	  case 3:
	      dest[di+2] = (byte)((plain0 >>> 8) & 0xff);
	  case 2:
	      dest[di+1] = (byte)((plain0 >>> 16) & 0xff);
	  case 1:
	      dest[di]   = (byte)((plain0 >>> 24) & 0xff);
	  }
      }
    }
    IV0 = iv0;
    IV1 = iv1;
  }

  public void setKey(byte[] key) {
      int i, ki = 0, j = 0;
      for(i = 0; i < 8; i++)
	  key_schedule[i] = ((key[2 * i] & 0xff) << 8) | (key[(2 * i) + 1] & 0xff);

      for(i = 8, j = 0; i < 52; i++) {
	  j++;
	  key_schedule[ki + j + 7] = ((key_schedule[ki + (j & 7)] << 9) |
				      (key_schedule[ki + ((j + 1) & 7)] >>> 7)) & 0xffff;
	  ki += j & 8;
	  j &= 7;
      }
  }

  public final void encrypt(int l, int r, int[] out) {
      int t1 = 0, t2 = 0, x1, x2, x3, x4, ki = 0;

      x1 = (l >>> 16);
      x2 = (l & 0xffff);
      x3 = (r >>> 16);
      x4 = (r & 0xffff);

      for(int round = 0; round < 8; round++) {
	  x1 = mulop(x1 & 0xffff, key_schedule[ki++]);
	  x2 = (x2 + key_schedule[ki++]);
	  x3 = (x3 + key_schedule[ki++]);
	  x4 = mulop(x4 & 0xffff, key_schedule[ki++]);

	  t1 = (x1 ^ x3);
	  t2 = (x2 ^ x4);
	  t1 = mulop(t1 & 0xffff, key_schedule[ki++]);
	  t2 = (t1 + t2);
	  t2 = mulop(t2 & 0xffff, key_schedule[ki++]);
	  t1 = (t1 + t2);

	  x1 = (x1 ^ t2);
	  x4 = (x4 ^ t1);
	  t1 = (t1 ^ x2);
	  x2 = (t2 ^ x3);
	  x3 = t1;
      }
  
      t2 = x2;
      x1 = mulop(x1 & 0xffff, key_schedule[ki++]);
      x2 = (t1 + key_schedule[ki++]);
      x3 = ((t2 + key_schedule[ki++]) & 0xffff);
      x4 = mulop(x4 & 0xffff, key_schedule[ki]);


      out[0] = (x1 << 16) | (x2 & 0xffff);
      out[1] = (x3 << 16) | (x4 & 0xffff);
  }

  public final void decrypt(int l, int r, int[] out) {
      encrypt(l, r, out);
  }

  public static final int mulop(int a, int b) {
      int ab = a * b;
      if(ab != 0) {
	  int lo = ab & 0xffff;
	  int hi = (ab >>> 16) & 0xffff;
	  return ((lo - hi) + ((lo < hi) ? 1 : 0));
      }
      if(a == 0)
	  return (1 - b);
      return  (1 - a);
  }

    /* !!! REMOVE DEBUG !!!

  public static void main(String[] argv) {
    byte[] key = { 
      (byte)0xd3, (byte)0x96, (byte)0xcf, (byte)0x07, (byte)0xfa, (byte)0xa2, (byte)0x64,
      (byte)0xfe, (byte)0xf3, (byte)0xa2, (byte)0x06, (byte)0x07, (byte)0x1a, (byte)0xb6,
      (byte)0x13, (byte)0xf6
    };

    byte[] txt = {
      (byte)0x2e, (byte)0xbe, (byte)0xc5, (byte)0xac, (byte)0x02, (byte)0xa1, (byte)0xd5, 
      (byte)0x7f, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x1f, (byte)0x43, 
      (byte)0x6f, (byte)0x72, (byte)0x72, (byte)0x75, (byte)0x70, (byte)0x74, (byte)0x65, 
      (byte)0x64, (byte)0x20, (byte)0x63, (byte)0x68, (byte)0x65, (byte)0x63, (byte)0x6b, 
      (byte)0x20, (byte)0x62, (byte)0x79, (byte)0x74, (byte)0x65, (byte)0x73, (byte)0x20, 
      (byte)0x6f, (byte)0x6e, (byte)0x20, (byte)0x69, (byte)0x6e, (byte)0x70, (byte)0x75, 
      (byte)0x74, (byte)0x2e, (byte)0x91, (byte)0x9a, (byte)0x57, (byte)0xdd
    };

    byte[] enc;
    byte[] dec;

    System.out.println("key: " + printHex(key));
    System.out.println("txt: " + printHex(txt));

    IDEA cipher = new IDEA();
    cipher.setKey(key);

    for(int i = 0; i < 52; i++) {
	if((i & 0x7) == 0)
	    System.out.println("");
	System.out.print(" " + cipher.key_schedule[i]);
    }

    enc = cipher.encrypt(txt);
    System.out.println("enc: " + printHex(enc));

    cipher = new IDEA();
    cipher.setKey(key);
    dec = cipher.decrypt(enc);

    System.out.println("dec: " + printHex(dec));
  }

  static String printHex(byte[] buf) {
    byte[] out = new byte[buf.length + 1];
    out[0] = 0;
    System.arraycopy(buf, 0, out, 1, buf.length);
    BigInteger big = new BigInteger(out);
    return big.toString(16);
  }

    */

}

